/****************************************************************************
 * Copyright (c) 2010 Torkild U. Resheim
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *    Torkild U. Resheim  - initial API and implementation
 *****************************************************************************/
package no.resheim.buildmonitor.ui;

import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Locale;
import java.util.Observable;
import java.util.Observer;

import no.resheim.buildmonitor.core.BuildmonitorCore;
import no.resheim.buildmonitor.core.BuildmonitorCore.ServerList;
import no.resheim.buildmonitor.core.data.Build;
import no.resheim.buildmonitor.core.data.IChangeReporter;
import no.resheim.buildmonitor.core.data.Job;
import no.resheim.buildmonitor.core.data.Server;
import no.resheim.buildmonitor.ui.notifications.NotificationPopup;

import org.eclipse.jface.resource.ImageRegistry;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.graphics.ImageData;
import org.eclipse.swt.graphics.ImageLoader;
import org.eclipse.swt.widgets.Display;
import org.eclipse.ui.plugin.AbstractUIPlugin;
import org.osgi.framework.BundleContext;

import com.ocpsoft.pretty.time.BasicTimeFormat;
import com.ocpsoft.pretty.time.Duration;
import com.ocpsoft.pretty.time.PrettyTime;
import com.ocpsoft.pretty.time.TimeFormat;
import com.ocpsoft.pretty.time.TimeUnit;
import com.ocpsoft.pretty.time.units.Day;
import com.ocpsoft.pretty.time.units.Hour;
import com.ocpsoft.pretty.time.units.Minute;
import com.ocpsoft.pretty.time.units.Month;
import com.ocpsoft.pretty.time.units.Second;
import com.ocpsoft.pretty.time.units.Week;
import com.ocpsoft.pretty.time.units.Year;

/**
 * 
 * @author Torkild Ulvøy Resheim
 * @since 1.0
 */
public class BuildmonitorUI extends AbstractUIPlugin implements Observer {

	class NotificationTimer implements Runnable {

		public void run() {
			synchronized (notificationItems) {
				if (notificationItems.size() > 0) {
					new NotificationPopup(
							Display.getDefault(),
							notificationItems
									.toArray(new IChangeReporter[notificationItems
											.size()]));
					notificationItems.clear();
				}
				Display display = Display.getDefault();
				display.timerExec(NOTIFICATION_TIMER_INTERVAL, this);
			}
		}
	}

	private static final String EMPTY_STRING = "";

	public static final String IMG_SERVER = "server";

	/**
	 * Wait at least five seconds between popping up a notification. If we have
	 * more events they will be displayed in the same notification.
	 */
	private static final int NOTIFICATION_TIMER_INTERVAL = 5000;

	private static BuildmonitorUI plugin;

	/** Used for formatting time in a nice human readable format */
	private static PrettyTime prettyTime = new PrettyTime();

	public static String formatBuild(Build b) {
		if (b == null || b.getTimestamp() == 0)
			return "unknown";
		StringBuilder sb = new StringBuilder();
		sb.append(prettyTime.format(new Date(b.getTimestamp())));
		sb.append(" (#");
		sb.append(b.getNumber());
		sb.append(")");
		return sb.toString();
	}

	public static final String PROGRESS = "PROGRESS";

	/** The default pretty time format */
	private static final TimeFormat timeFormat = new BasicTimeFormat()
			.setPattern("%n %u").setPastSuffix(EMPTY_STRING)
			.setFutureSuffix(EMPTY_STRING);

	private static List<TimeUnit> units;

	private static Duration calculateDuration(final long difference) {
		long absoluteDifference = Math.abs(difference);

		Duration result = new Duration();

		for (int i = 0; i < units.size(); i++) {
			TimeUnit unit = units.get(i);
			long millisPerUnit = Math.abs(unit.getMillisPerUnit());
			long quantity = Math.abs(unit.getMaxQuantity());

			boolean isLastUnit = (i == units.size() - 1);

			if ((quantity == 0) && !isLastUnit) {
				quantity = units.get(i + 1).getMillisPerUnit()
						/ unit.getMillisPerUnit();
			}

			// does our unit encompass the time duration?
			if ((millisPerUnit * quantity > absoluteDifference) || isLastUnit) {
				result.setUnit(unit);
				if (millisPerUnit > absoluteDifference) {
					// we are rounding up: get 1 or -1 for past or future
					result.setQuantity(getSign(difference, absoluteDifference));
				} else {
					result.setQuantity(difference / millisPerUnit);
				}
				result.setDelta(difference - result.getQuantity()
						* millisPerUnit);
				break;
			}

		}
		return result;
	}

	public static String formatDuration(long d) {
		if (d == 0) {
			return EMPTY_STRING;
		}
		StringBuilder sb = new StringBuilder();
		sb.append(timeFormat.format(calculateDuration(d)));
		return sb.toString();
	}

	/**
	 * Returns the shared instance
	 * 
	 * @return the shared instance
	 */
	public static BuildmonitorUI getDefault() {
		return plugin;
	}

	ArrayList<IChangeReporter> notificationItems = new ArrayList<IChangeReporter>();

	private int[] progressDelay;
	private int progressImageCount;

	{
		units = new ArrayList<TimeUnit>();
		units.add(new Second(Locale.getDefault()));
		units.add(new Minute(Locale.getDefault()));
		units.add(new Hour(Locale.getDefault()));
		units.add(new Day(Locale.getDefault()));
		units.add(new Week(Locale.getDefault()));
		units.add(new Month(Locale.getDefault()));
		units.add(new Year(Locale.getDefault()));
	}

	public BuildmonitorUI() {
		progressDelay = new int[0];
	}

	public int[] getProgressDelay() {
		return progressDelay;
	}

	/**
	 * Returns the number of images in the animated GIF we use for progress
	 * indication.
	 * 
	 * @return the number of images
	 */
	public int getProgressImageCount() {
		return progressImageCount;
	}

	private static long getSign(final long difference,
			final long absoluteDifference) {
		if (difference < 0) {
			return -1;
		} else {
			return 1;
		}
	}

	@Override
	protected void initializeImageRegistry(ImageRegistry reg) {
		super.initializeImageRegistry(reg);
		reg.put(IMG_SERVER,
				imageDescriptorFromPlugin(getBundle().getSymbolicName(),
						"icons/obj16/server.gif"));
		reg.put(Job.Health.ERROR.name(),
				imageDescriptorFromPlugin(this.getBundle().getSymbolicName(),
						"icons/obj16/ERROR.gif"));
		reg.put(Job.Health.FAILED.name(),
				imageDescriptorFromPlugin(this.getBundle().getSymbolicName(),
						"icons/obj16/FAILED.gif"));
		reg.put(Job.Health.GOOD.name(),
				imageDescriptorFromPlugin(this.getBundle().getSymbolicName(),
						"icons/obj16/GOOD.gif"));
		reg.put(Job.Health.UNKNOWN.name(),
				imageDescriptorFromPlugin(this.getBundle().getSymbolicName(),
						"icons/obj16/UNKNOWN.gif"));
		reg.put(Job.Health.DISABLED.name(),
				imageDescriptorFromPlugin(this.getBundle().getSymbolicName(),
						"icons/obj16/DISABLED.gif"));
		reg.put(Job.Health.ABORTED.name(),
				imageDescriptorFromPlugin(this.getBundle().getSymbolicName(),
						"icons/obj16/ABORTED.gif"));
		ImageLoader loader = new ImageLoader();
		loader.load(getClass().getResourceAsStream("/icons/obj16/PROGRESS.gif"));
		progressImageCount = loader.data.length;
		progressDelay = new int[loader.data.length];
		for (int x = 0; x < progressImageCount; x++) {
			ImageData id = loader.data[x];
			progressDelay[x] = loader.data[x].delayTime;
			Image image = new Image(Display.getCurrent(), id);
			reg.put(PROGRESS + "_" + x, image);
		}
	}

	@Override
	public void start(BundleContext context) throws Exception {
		super.start(context);
		plugin = this;
		// Observe the server list
		ServerList sl = BuildmonitorCore.getDefault().getServers();
		sl.addObserver(this);
		// And each of the existing servers
		for (Server s : sl.getServers()) {
			s.addObserver(this);
		}
		// Create the timer that will pop up a notification if we have any items
		// in the notificationItems list.
		NotificationTimer timer = new NotificationTimer();
		Display display = Display.getDefault();
		display.asyncExec(timer);
	}

	@Override
	public void stop(BundleContext context) throws Exception {
		plugin = null;
		super.stop(context);
	}

	@Override
	public void update(Observable o, final Object arg) {
		if (arg != null && arg instanceof IChangeReporter
				&& ((IChangeReporter) arg).getChangeDescription() != null) {
			notificationItems.add((IChangeReporter) arg);
		}
	}

}
